/*
 * Copyright (c) 2013-2018 上海汇数数据. All rights reserved.
 * @(#) JsonKt.java 2018-08-06 16:52
 */

package cn.jh.common.core.json;

import com.google.common.collect.ImmutableList;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.nio.file.OpenOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.google.common.base.Preconditions.checkArgument;
import static java.nio.charset.StandardCharsets.UTF_8;


/**
 * The JSON wrapped helper class.
 *
 * @author Fuchun
 * @since 1.0
 */
public abstract class JsonKt {

    private static final boolean jacksonPresent = classPresent("com.fasterxml.jackson.databind.ObjectMapper");
    private static final boolean fastJsonPresent = classPresent("com.alibaba.fastjson.JSON");
    private static final Logger LOGGER = LoggerFactory.getLogger(JsonKt.class);

    private static JsonOperator jsonOperator;

    static {
        if (jacksonPresent) {
            jsonOperator = new JacksonJsonOperator();
            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace("FasterXML jackson library exists, to use it.");
            }
        } else if (fastJsonPresent) {
            jsonOperator = new FastJsonOperator();
            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace("Alibaba Fastjson library exists, to use it.");
            }
        } else {
            throw new InternalError("JSON must be reference Jackson or Alibaba fastjson component");
        }
    }

    private static boolean classPresent(String className) {
        try {
            Class.forName(className);
            return true;
        } catch (ClassNotFoundException ex) {
            return false;
        }
    }

    /**
     * 反序列化一个JSON字符串列表，一个JSON字符串对应一个反序列化对象。如果{@code skipNull == true}，
     * 在反序列化时，将忽略JSON列表中的{@code null} 和空字符串。
     * <pre>{@code
     * List<String> jsonList = Arrays.asList("{\"l\":1,\"r\":2}", null, "{\"l\":3,\"r\":5}");
     * List<Pair<Integer, Integer>> pairClass = new TypeToken<Pair<Integer, Integer>>(){}.getRawType();
     * List<Pair<Integer, Integer>> list = JSON.deserialize(jsonList, pairClass, true);
     * // list.size() == 2
     * List<Pair<Integer, Integer>> list2 = JSON.deserialize(jsonList, pairClass, false);
     * // list.size() == 3
     * }</pre>
     *
     * @param jsonList JSON字符串列表。
     * @param elementType 列表元素类型。
     * @param skipNull 是否忽略{@code null}和空元素。
     * @param <E> 元素泛型类型。
     * @return 返回反序列化后的列表。
     */
    @NotNull
    public static <E> List<E> deserialize(
            List<String> jsonList, Class<E> elementType, boolean skipNull) {
        checkArgument(elementType != null, "elementType");
        if (jsonList == null || jsonList.isEmpty()) {
            return ImmutableList.of();
        }
        List<E> list = new ArrayList<>(jsonList.size());
        int i = 0;
        for (String json : jsonList) {
            if (json == null || json.isEmpty()) {
                if (skipNull) continue;
                list.add(i, null);
            } else {
                list.add(i, readValue(json, elementType));
            }
            i++;
        }
        return list;
    }

    /**
     * 将给定的{@code Map} 转换成指定类型的对象。
     * <pre>{@code
     * Foo foo = JSON.convertValue(null, Foo.class);
     * // foo is null
     * foo = JSON.convertValue(ImmutableMap.of(), Foo.class);
     * // foo is not null (Just invoked constructor: new Foo())
     * }</pre>
     *
     * @param map 属性映射。
     * @param targetType 要转换的类型。
     * @param <T> 转换的类型泛型。
     * @return 返回转换后的对象。
     * @throws NullPointerException 如果指定的类型{@code targetType == null}。
     * @throws JsonParseException 如果在转换的过程中发生错误。
     */
    @Nullable
    @SuppressWarnings("TypeParameterUnusedInFormals")
    public static <T> T convertValue(Map<String, Object> map, Class<T> targetType) {
        return jsonOperator.convert(map, targetType);
    }

    @Contract("null, _ -> null; _, null -> fail")
    @SuppressWarnings("TypeParameterUnusedInFormals")
    public static <T> T convertValue(Map<String, Object> map, Type type) {
        return jsonOperator.convert(map, type);
    }

    /**
     * 将指定的对象序列化JSON格式字符串。
     *
     * @param target 要序列化的对象。
     * @return 返回指定对象的JSON格式字符串。
     * @throws JsonParseException 如果序列化发生错误。
     */
    public static String toJSONString(Object target) {
        return jsonOperator.toJSONString(target);
    }

    /**
     * 将指定的对象序列化JSON格式字符串。
     *
     * @param target 要序列化的对象。
     * @param viewClass 显示视图的过滤器类。
     * @return 返回指定对象的JSON格式字符串。
     * @throws JsonParseException 如果序列化发生错误。
     * @since 1.1
     */
    public static String toJSONString(Object target, Class<?> viewClass) {
        return jsonOperator.toJSONString(target, viewClass);
    }

    /**
     * 将指定的对象序列化JSON格式字符串。
     *
     * @param target 要序列化的对象。
     * @param viewClass 显示视图的过滤器类。
     * @param prettyPrinter 是否输出可读性的格式。
     * @return 返回指定对象的JSON格式字符串。
     * @throws JsonParseException 如果序列化发生错误。
     * @since 1.1
     */
    public static String toJSONString(Object target, Class<?> viewClass, boolean prettyPrinter) {
        return jsonOperator.toJSONString(target, viewClass, prettyPrinter);
    }

    /**
     * 将指定的对象序列化JSON格式字符串。
     *
     * @param target 要序列化的对象。
     * @param prettyPrinter 是否输出可读性的格式。
     * @return 返回指定对象的JSON格式字符串。
     * @throws JsonParseException 如果序列化发生错误。
     */
    public static String toJSONString(Object target, boolean prettyPrinter) {
        return jsonOperator.toJSONString(target, prettyPrinter);
    }

    /**
     * 将指定的对象序列化JSON格式数据。
     *
     * @param target 要序列化的对象。
     * @return 返回指定对象的JSON格式数据。
     * @throws JsonParseException 如果序列化发生错误。
     */
    public static byte[] toJSONBytes(Object target) {
        return jsonOperator.toJSONBytes(target);
    }

    public static <T> T readValue(String json, Class<T> clazz) {
        return jsonOperator.parseObject(json, clazz);
    }

    public static <T> T readValue(byte[] json, Class<T> clazz) {
        return readValue(json, (Type) clazz);
    }

    @SuppressWarnings("TypeParameterUnusedInFormals")
    public static <T> T readValue(String json, Type type) {
        return jsonOperator.parseObject(json, type);
    }

    @SuppressWarnings("TypeParameterUnusedInFormals")
    public static <T> T readValue(byte[] json, Type type) {
        return readValue(new String(json, UTF_8), type);
    }

    public static <T> T readValue(File file, Class<T> valueClass) throws IOException {
        return jsonOperator.parseObject(file, valueClass);
    }

    @Contract("null, _ -> fail; _, null -> fail")
    @SuppressWarnings("TypeParameterUnusedInFormals")
    public static <T> T readValue(File file, Type valueType) throws IOException {
        return jsonOperator.parseObject(file, valueType);
    }

    @Contract("null, _ -> fail; _, null -> fail")
    public static <T> T readValue(InputStream input, Class<T> valueClass) throws IOException {
        return jsonOperator.parseObject(input, valueClass);
    }

    @Contract("null, _ -> fail; _, null -> fail")
    @SuppressWarnings("TypeParameterUnusedInFormals")
    public static <T> T readValue(InputStream input, Type valueType) throws IOException {
        return jsonOperator.parseObject(input, valueType);
    }

    @SuppressWarnings("unchecked")
    public static <T> List<T> readToList(String json, Type elementType) {
        return jsonOperator.parseToList(json, elementType);
    }

    public static <T> List<T> readToList(String json, Class<T> elementType) {
        return jsonOperator.parseToList(json, elementType);
    }

    public static <T> Set<T> readToSet(String json, Type elementType) {
        return jsonOperator.parseToSet(json, elementType);
    }

    public static <T> Set<T> readToSet(String json, Class<T> elementType) {
        return jsonOperator.parseToSet(json, elementType);
    }

    public static Map<String, Object> readToMap(String json) {
        return readToMap(json, String.class, Object.class);
    }

    public static <K, V> Map<K, V> readToMap(String json, Type keyType, Type valueType) {
        return jsonOperator.parseToMap(json, keyType, valueType);
    }

    public static <K, V> Map<K, V> readToMap(String json, Class<K> keyClass, Class<V> valueClass) {
        return jsonOperator.parseToMap(json, keyClass, valueClass);
    }

    @Contract("null, _, _ -> fail; _, null, _ -> fail")
    public static void writeValue(File file, Object o, OpenOption... options) throws IOException {
        jsonOperator.writeValue(file, o, options);
    }

    @Contract("null, _ -> fail; _, null -> fail")
    public static void writeValue(OutputStream output, Object o) throws IOException {
        jsonOperator.writeValue(output, o);
    }

    public static JsonOperator getJsonOperator() {
        return JsonKt.jsonOperator;
    }

    public static synchronized void setJsonOperator(JsonOperator jsonOperator) {
        JsonKt.jsonOperator = jsonOperator;
    }

    private JsonKt() {}
}
